
#include "my_common.h"

static const char *TAG = "mycommon";

static system_status_t g_sys_sta = {0};
struct tm timeinfo;

static task_handle_managa_t g_task_handle_haed = {0};
static task_handle_managa_t *g_task_handle_end = NULL;

#if ENABLE_KEY_FUNCTIONS
static uint8_t g_key_value = KEY_SET_UP;

static void KEY_scanf()
{
	gpio_key_s *pst = &g_sys_sta.key_info;
	uint32_t curtime = esp_get_time() / 1000;

	if (gpio_get_level(GPIO_KEY_PIN) == KEY_SET_DOWN)
		g_key_value = KEY_SET_DOWN;
	else 
		g_key_value = KEY_SET_UP;

	if (g_key_value == KEY_SET_DOWN) {
		//start time
		if (pst->dowmFlag == 0)
		{
			pst->dowmFlag = 1;
			pst->dowmStartTime = curtime;
		}
		//shortdown
		if ((pst->shortdowncb != NULL) && (pst->shortdownIsWork == 0) &&
				(pst->shortdown_ms <= (curtime - pst->dowmStartTime)))
		{
			pst->shortdownIsWork = 1;
			pst->shortdowncb();
		}

		//若shortdown已经生效，则去检测longdown，否则直接退出
		if (pst->shortdownIsWork == 0)
			return ;

		//longdown
		if ((pst->londdowncb != NULL) && (pst->longdownIsWork == 0) &&
				(pst->longdown_ms <= (curtime - pst->dowmStartTime)))
		{
			pst->longdownIsWork = 1;
			pst->londdowncb();
		}
	}
	else {
		pst->dowmFlag = 0;
		pst->shortdownIsWork = 0;
		pst->longdownIsWork = 0;
	}
}
#endif

system_status_t *get_sys_sta(void)
{
	return &g_sys_sta;
}

int set_key_shortDown_callback(int worktime, keyDownCallback fn)
{
	gpio_key_s *pst = &g_sys_sta.key_info;
	
	pst->shortdown_ms = worktime;
	pst->shortdowncb = fn;
	return 0;
}

int set_key_longDown_callback(int worktime, keyDownCallback fn)
{
	gpio_key_s *pst = &g_sys_sta.key_info;
	pst->longdown_ms = worktime;
	pst->londdowncb = fn;
	return 0;
}

#if ENABLE_LED_FUNCTIONS
static void led_control(void)
{
	static uint16_t old_time = 0;
	uint32_t cur_time = esp_get_time() / 1000;
	system_status_t *sys_sta = get_sys_sta();

	uint8_t mode = 0;

	if (sys_sta->led_mode.LED_OPEN) 
		mode = 1;
	
	if (sys_sta->led_mode.LED_SLOW_BLINK)
		mode = 2;

	if (sys_sta->led_mode.LED_FAST_BLINK)
		mode = 3;

	switch (mode)
	{
	case 0:
		if (sys_sta->led_sta == LED_WORK_UP) {
			gpio_set_level(GPIO_LED_PIN, LED_WORK_DOWN);
			sys_sta->led_sta = LED_WORK_DOWN;
		}
		break;
	case 1:
		if (sys_sta->led_sta == LED_WORK_DOWN) {
			gpio_set_level(GPIO_LED_PIN, LED_WORK_UP);
			sys_sta->led_sta = LED_WORK_UP;
		}
		break;
	case 2:
		if ((cur_time - old_time) >= 1000){
			if (sys_sta->led_sta == LED_WORK_UP){
				gpio_set_level(GPIO_LED_PIN, LED_WORK_DOWN);
				sys_sta->led_sta = LED_WORK_DOWN;
			}
			else {
				gpio_set_level(GPIO_LED_PIN, LED_WORK_UP);
				sys_sta->led_sta = LED_WORK_UP;
			}
			old_time = cur_time;
		}
		break;
	case 3:
		if ((cur_time - old_time) >= 100){
			if (sys_sta->led_sta == LED_WORK_UP){
				gpio_set_level(GPIO_LED_PIN, LED_WORK_DOWN);
				sys_sta->led_sta = LED_WORK_DOWN;
			}
			else {
				gpio_set_level(GPIO_LED_PIN, LED_WORK_UP);
				sys_sta->led_sta = LED_WORK_UP;
			}
			old_time = cur_time;
		}
		break;
	default:
		break;
	}
}
#endif

static void Gpio_Init(void)
{
#if ENABLE_LED_FUNCTIONS || ENABLE_KEY_FUNCTIONS
	gpio_config_t IO_config;

#if ENABLE_LED_FUNCTIONS
	IO_config.pin_bit_mask = GPIO_LED_MASK;
	IO_config.mode = GPIO_MODE_OUTPUT;
	IO_config.pull_up_en = GPIO_PULLUP_ENABLE;
	IO_config.pull_down_en = GPIO_PULLDOWN_DISABLE;
	IO_config.intr_type = GPIO_INTR_DISABLE;
	gpio_config(&IO_config);
	gpio_set_level(GPIO_LED_PIN, LED_WORK_DOWN);
	g_sys_sta.led_sta = LED_WORK_DOWN;
#endif

#if ENABLE_KEY_FUNCTIONS
	IO_config.pin_bit_mask = GPIO_KEY_MASK;
	IO_config.mode = GPIO_MODE_INPUT;
	IO_config.pull_up_en = GPIO_PULLUP_ENABLE;
	IO_config.pull_down_en = GPIO_PULLDOWN_DISABLE;
	IO_config.intr_type = GPIO_INTR_DISABLE;
	gpio_config(&IO_config);
#endif
#endif
}

static void system_monitor(void *argv)
{
	int count = 0;
	int ret = 0;
	system_status_t *sys_sta = get_sys_sta();
	while (1)
	{
#if ENABLE_KEY_FUNCTIONS
		KEY_scanf();
#endif

#if ENABLE_LED_FUNCTIONS
		led_control();
#endif
		if (sys_sta->reboot_flag == 1 && g_sys_sta.key_info.longdownIsWork == 0) {
			vTaskDelay(100/portTICK_PERIOD_MS);
			esp_restart();
		}

		if (sys_sta->sys_cb) {
			sys_sta->sys_cb();
		}

		if (count > 100 && count < 200) {
			count = 0;
			size_t len = sizeof(count);
			ret = my_nvs_data_rw(FAST_REBOOT_NUM_KEY, &count, &len, MY_NVS_I32_TYPE, NVS_READWRITE);
			if (ret != 0) {
				printf("error: clear %s failed\n", FAST_REBOOT_NUM_KEY);
				count = 100;
			} else {
				count = 200;
			}
		} else {
			count++;
		}
		vTaskDelay(100/portTICK_PERIOD_MS);
	}
}

static task_handle_managa_t *find_prev_task_handle(const char *pname)
{
	task_handle_managa_t *task_handle_prev = NULL;
	
	for (task_handle_prev = &g_task_handle_haed; task_handle_prev->next != NULL; 
			task_handle_prev = task_handle_prev->next)
	{
		if (strcmp(task_handle_prev->next->name, pname) == 0) {
			break;
		}
	}
	return task_handle_prev;
}

#if (HEAPSTACK_MONITOR)
static void HeapStack_monitor(void *argv)
{
	task_handle_managa_t *task_handle_cur_p = NULL;
	task_handle_managa_t *task_handle_haed_p = &g_task_handle_haed;
	while (1)
	{
		printf("\n");
		ESP_LOGI(TAG, "free_heap_size: %d KB", esp_get_free_heap_size() / 1024);
		ESP_LOGI(TAG, "minimum_free_heap_size: %d KB", esp_get_minimum_free_heap_size() / 1024);

		for (task_handle_cur_p = task_handle_haed_p; task_handle_cur_p != NULL; 
				task_handle_cur_p = task_handle_cur_p->next)
		{
			if (task_handle_cur_p->handle != NULL) {
				ESP_LOGI(TAG, "%s stack:%d B", task_handle_cur_p->name, (uint32_t)(uxTaskGetStackHighWaterMark(task_handle_cur_p->handle)) * 4);
			}
		}
		printf("\n");
		vTaskDelay(1000/portTICK_PERIOD_MS);
	}
}
#endif

void my_user_nvs_init(void)
{
	esp_err_t err = nvs_flash_init();
	if (err == ESP_ERR_NVS_NO_FREE_PAGES) {
		ESP_ERROR_CHECK(nvs_flash_erase());
		err = nvs_flash_init();
	}
	ESP_ERROR_CHECK( err );

	nvs_handle my_handle;
	err = nvs_open(NVS_SYSTEM_DATA_SPACE, NVS_READWRITE, &my_handle);
	if (err != ESP_OK) {
		printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));
	} 
	int32_t restart_counter = 0;
	err = nvs_get_i32(my_handle, POWER_ON_COUNT_KEY, &restart_counter);
	switch (err) {
		case ESP_OK:
			printf("%s = %d\n", POWER_ON_COUNT_KEY, restart_counter);
			break;
		case ESP_ERR_NVS_NOT_FOUND:
			printf("%s is not initialized yet!\n", POWER_ON_COUNT_KEY);
			break;
		default :
			printf("Error (%s) reading!\n", esp_err_to_name(err));
	}
	int32_t fast_up_num = 0;
	err = nvs_get_i32(my_handle, FAST_REBOOT_NUM_KEY, &fast_up_num);
	switch (err) {
		case ESP_OK:
			printf("%s = %d\n", FAST_REBOOT_NUM_KEY, fast_up_num);
			break;
		case ESP_ERR_NVS_NOT_FOUND:
			printf("%s is not initialized yet!\n", FAST_REBOOT_NUM_KEY);
			break;
		default :
			printf("Error (%s) reading!\n", esp_err_to_name(err));
	}

	if (fast_up_num >= 4) {
		my_nvs_info_erase();
	}

	restart_counter++;
	fast_up_num++;
	err = nvs_set_i32(my_handle, POWER_ON_COUNT_KEY, restart_counter);
	if (err != ESP_OK)
			printf("Error (%s) seting!\n", esp_err_to_name(err));
	err = nvs_set_i32(my_handle, FAST_REBOOT_NUM_KEY, fast_up_num);
	if (err != ESP_OK)
			printf("Error (%s) seting!\n", esp_err_to_name(err));
	err = nvs_commit(my_handle);
	if (err != ESP_OK)
			printf("Error (%s) seting!\n", esp_err_to_name(err));
	nvs_close(my_handle);
}

int my_nvs_data_rw(const char *key, void *buf, size_t *len, my_nvs_data_type_e type, nvs_open_mode mode)
{
	int ret = 0;
	esp_err_t err;
	nvs_handle my_handle;
	err = nvs_open(NVS_SYSTEM_DATA_SPACE, mode, &my_handle);
	if (err != ESP_OK) {
		printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));
		ret = -1;
		return ret;
	}

	if (mode == NVS_READONLY) {
		switch (type)
		{
		case MY_NVS_I32_TYPE:
			err = nvs_get_i32(my_handle, key, (int32_t *)buf);
			break;
		case MY_NVS_I64_TYPE:
			err = nvs_get_i64(my_handle, key, (int64_t *)buf);
			break;
		case MY_NVS_STR_TYPE:
			err = nvs_get_str(my_handle, key, (char *)buf, len);
			break;
		case MY_NVS_BLOB_TYPE:
			err = nvs_get_blob(my_handle, key, buf, len);
			break;
		default:
			break;
		}

		switch (err) {
			case ESP_OK:
				printf("%s read %d Byte OK, \n", key, *len);
				break;
			case ESP_ERR_NVS_NOT_FOUND:
				printf("%s is not initialized yet!\n", key);
				ret = -1;
				break;
			default :
				printf("Error (%s) reading!\n", esp_err_to_name(err));
				ret = -1;
		}
	} else {
		switch (type)
		{
		case MY_NVS_I32_TYPE:
			err = nvs_set_i32(my_handle, key, *(int32_t *)buf);
			break;
		case MY_NVS_I64_TYPE:
			err = nvs_set_i64(my_handle, key, *(int64_t *)buf);
			break;
		case MY_NVS_STR_TYPE:
			err = nvs_set_str(my_handle, key, (const char *)buf);
			break;
		case MY_NVS_BLOB_TYPE:
			err = nvs_set_blob(my_handle, key, buf, *len);
			break;
		default:
			break;
		}

		if (err != ESP_OK) {
			printf("Error (%s) seting!\n", esp_err_to_name(err));	
			ret = -1;
		}
	}

	err = nvs_commit(my_handle);
	if (err != ESP_OK) {
		printf("Error (%s) seting!\n", esp_err_to_name(err));
		ret = -1;
	}
	nvs_close(my_handle);

	return ret;
}

void my_nvs_info_erase(void)
{
	nvs_handle my_handle;
	esp_err_t err = nvs_open("storage", NVS_READWRITE, &my_handle);
	if (err != ESP_OK) {
		printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));
	}
	// err = nvs_erase_key(my_handle, "wifi_ssid");
	// err = nvs_erase_key(my_handle, "wifi_passwd");
	// err = nvs_erase_key(my_handle, "restart_counter");
	err = nvs_erase_all(my_handle);
	err = nvs_commit(my_handle);
	if (err != ESP_OK)
		printf("Error (%s) erase!\n", esp_err_to_name(err));
	nvs_close(my_handle);
}

int my_nvs_wifi_info_get(my_wifi_t *wifi_info)
{
	size_t len = 0;
	int ret = 0;

	len = sizeof(wifi_info->ssid);
	ret = my_nvs_data_rw(WIFI_SSID_KEY, wifi_info->ssid, &len, MY_NVS_STR_TYPE, NVS_READONLY);
	if (ret) {
		return -1;
	}
	printf("read ssid : %s \n", wifi_info->ssid);

	len = sizeof(wifi_info->password);
	ret = my_nvs_data_rw(WIFI_PASSWD_KEY, wifi_info->password, &len, MY_NVS_STR_TYPE, NVS_READONLY);
	if (ret) {
		return -1;
	}
	printf("read passwd : %s \n", wifi_info->password);

	return 0;
}

int my_nvs_wifi_info_set(char *ssid, char *passwd)
{
	int ret = 0;
	size_t len = strlen(ssid);
	ret = my_nvs_data_rw(WIFI_SSID_KEY, ssid, &len, MY_NVS_STR_TYPE, NVS_READWRITE);
	if (ret) {
		return -1;
	}

	len = strlen(passwd);
	ret = my_nvs_data_rw(WIFI_PASSWD_KEY, passwd, &len, MY_NVS_STR_TYPE, NVS_READWRITE);
	if (ret) {
		return -1;
	}

	return 0;
}

void new_task(TaskFunction_t pxTaskCode, const char * const pcName, 
						const configSTACK_DEPTH_TYPE usStackDepth,
						void * const pvParameters,UBaseType_t uxPriority)
{
	task_handle_managa_t *task_handle_cur = NULL;

	task_handle_cur = calloc(1, sizeof(task_handle_managa_t));
	if (g_task_handle_haed.next == NULL)
		g_task_handle_haed.next = task_handle_cur;
	else
		g_task_handle_end->next = task_handle_cur;
	g_task_handle_end = task_handle_cur;

	snprintf(task_handle_cur->name, sizeof(task_handle_cur->name), pcName);
	ESP_LOGW(TAG,"create %s task", pcName);
	xTaskCreate(pxTaskCode, pcName, usStackDepth, pvParameters, uxPriority, &task_handle_cur->handle);
}

void delete_task(const char * const pcName)
{
	task_handle_managa_t *task_handle_prev = find_prev_task_handle(pcName);
	task_handle_managa_t *task_handle_cur = task_handle_prev->next;
	TaskHandle_t handle = NULL;

	if (task_handle_cur) {
		task_handle_prev->next = task_handle_cur->next;
		handle = task_handle_cur->handle;
		free(task_handle_cur);
		vTaskDelete(handle);
	}
}

static void initialize_sntp(void)
{
	ESP_LOGI(TAG, "Initializing SNTP");
	sntp_stop();
	sntp_setoperatingmode(SNTP_OPMODE_POLL);
	sntp_setservername(0, "cn.pool.ntp.org");
	sntp_init();
}

#if SUPPORT_FIRMWARE_OTA
static int edition_compare(const char* pszStr1, const char* pszStr2)
{
	if (pszStr1 == NULL || pszStr2 == NULL) {
		return 0;
	}
	int nCurPos = 0, nCapPos=-1;
	const char* pszTmp1 = pszStr1;
	const char* pszTmp2 = pszStr2;
	while ((*pszTmp1 != '\0') && (*pszTmp2 != '\0') && (*pszTmp1 == *pszTmp2)) {	
		nCurPos++;									//找到第一个处不相同出现的位置
		pszTmp1++;
		pszTmp2++;
		if (*pszTmp1 == '.') {
			nCapPos = nCurPos;						//记录最近的‘.’的位置
		}
	}
	if (*pszTmp1 == '\0' && *pszTmp2 == '\0') { // 两个字符串相等
		return 0;
	} else if(*pszTmp1 == '\0'){
		return -1;
	} else if(*pszTmp2 == '\0'){
		return 1;
	}else{ // 两个字符串不相等，比较大小
		pszTmp1 = pszStr1 + nCapPos + 1;
		pszTmp2 = pszStr2 + nCapPos + 1;

		int pszNub1=strtol(pszTmp1,NULL,10);
		int pszNub2=strtol(pszTmp2,NULL,10);

		return (pszNub1 - pszNub2);
	}
}

static void update_check(void)
{
	system_status_t *sys_sta = get_sys_sta();
	char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};
	int recv = 0;

	if (sys_sta->ota_version_path == NULL) {
		ESP_LOGE(TAG, "ota_version_path err");
		return;
	}

	esp_http_client_config_t config = {
		.method = HTTP_METHOD_GET,
		.url = sys_sta->ota_version_path,
		.user_data = local_response_buffer,
	};
	memset(local_response_buffer, 0, MAX_HTTP_OUTPUT_BUFFER);
	recv = http_client_GET(&config);

	if (recv > 6) {
		ESP_LOGE(TAG, "ota version err");
	    printf("rec:%d\n%s\n", recv, local_response_buffer);
		return;
	}

	if (local_response_buffer[strlen(local_response_buffer) - 1] == '\n') 
		local_response_buffer[strlen(local_response_buffer) - 1] = '\0';

	recv = edition_compare(local_response_buffer, sys_sta->version);
	if (recv > 0) {
		sys_sta->ota_sta = 1;
		ESP_LOGW(TAG, "%s -> %s", sys_sta->version, local_response_buffer);
		if (sys_sta->ota_image_path == NULL) {
			ESP_LOGE(TAG, "ota_image_path err");
			return;
		}
		recv = start_ota(sys_sta->ota_image_path);
		if (recv <= 0) {
			sys_sta->ota_sta = 0;
				ESP_LOGE(TAG, "%s -> %s : update fail", sys_sta->version, local_response_buffer);
		}
	}
	else {
		ESP_LOGW(TAG, "local ver: %s >= remote ver:%s", sys_sta->version, local_response_buffer);
	}
}
#endif

static void update_and_sntp_task(void *argv)
{
	system_status_t *sys_sta = get_sys_sta();
	time_t now;

	static uint8_t sync_sta = 0;

	time(&now);
	localtime_r(&now, &timeinfo);

	setenv("TZ", "CST-8", 1);
	tzset();
	while (1)
	{
		while (sys_sta->network_sta)
		{
			time(&now);
			localtime_r(&now, &timeinfo);
			
			if (timeinfo.tm_min == 0) {
				if (sync_sta == 0) {
					ESP_LOGW(TAG, "auto get ntp time");
					initialize_sntp();

#if SUPPORT_FIRMWARE_OTA
					ESP_LOGW(TAG, "check update");
					update_check();
#endif
					sync_sta = 1;
				}
			} else {
				sync_sta = 0;
			}

			// printf("%d/%d/%d ", 1900 + timeinfo.tm_year, timeinfo.tm_mon, timeinfo.tm_mday);
			// printf("%02d:%02d:%02d\n", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
			vTaskDelay(30000/portTICK_RATE_MS);
		}
		vTaskDelay(5000/portTICK_RATE_MS);
	}
}


void system_init(void)
{
	my_user_nvs_init();
	Gpio_Init();
	new_task(system_monitor, "system_monitor", 1024 * 10, NULL, 22);

#if HEAPSTACK_MONITOR
	new_task(HeapStack_monitor, "HeapStack_monitor", 1024 * 4, NULL, 23);
#endif

	new_task(update_and_sntp_task, "update_and_sntp_task", 1024 * 4, NULL, 24);
}

